package com.psytech.app.task.tasks_mds.service.impl;

import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.UUID;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import taskmanager.psytech.com.taskmanagerservice._2.Comment;
import taskmanager.psytech.com.taskmanagerservice._2.CommentList;
import taskmanager.psytech.com.taskmanagerservice._2.ObjectFactory;
import taskmanager.psytech.com.taskmanagerservice._2.PriorityType;
import taskmanager.psytech.com.taskmanagerservice._2.StatusType;
import taskmanager.psytech.com.taskmanagerservice._2.Task;
import taskmanager.psytech.com.taskmanagerservice._2.TaskList;
import taskmanager.psytech.com.taskmanagerservice._2.Todo;
import taskmanager.psytech.com.taskmanagerservice._2.UserUidList;

import com.psytech.app.task.tasks_mds.entity.EntityComment;
import com.psytech.app.task.tasks_mds.entity.EntityTask;
import com.psytech.app.task.tasks_mds.entity.EntityTodo;
import com.psytech.app.task.tasks_mds.entity.EntityTask.PRIORITY;
import com.psytech.app.task.tasks_mds.entity.EntityTask.STATUS;
import com.psytech.app.task.tasks_mds.entity.embeddable.TodoKey;
import com.psytech.app.task.tasks_mds.service.TaskMasterDataService;
import com.psytech.app.task.tasks_mds.service.exception.BusinessException;
import com.psytech.app.task.tasks_mds.service.exception.TechnicalException;

public class TaskMasterDataServiceImpl extends AbstractService implements TaskMasterDataService {

	private static final Logger LOG = Logger.getLogger(TaskMasterDataServiceImpl.class);

	private final static ObjectFactory OBJECT_FACTORY = new ObjectFactory();

	private class Error {
		public static final String TASK_UID_MISSING = "L'uid de la tâche doit être précisée";

		public static final String COMMENT_UID_MISSING = "L'uid du commentaire doit être précisée";

		public static final String COMMENT_WRITER_UID_MISSING = "L'uid de l'auteur du commentaire est absent";

		public static final String COMMENT_TASK_UID_MISSING = "L'uid de la tâche est absent";

		public static final String COMMENT_TEXT_MISSING = "Le commentaire ne contient pas de texte";

		public static final String DATE_PROBLEM = "Erreur lors de la convertion en date XML";

		public static final String TASK_UPDATE_FAILED = "La mise à jour de la tâche a échouée";

		public static final String TASK_SEARCH_FAILED = "Erreur lors de la recherche de la tâche";
	}

	@Override
	public Task completeTask(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setStatus(STATUS.COMPLETE);
		existingTask.setEndDate(new Date());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task createTask(final Task task) throws BusinessException, TechnicalException {
		if ((task.getUid() == null) || task.getUid().equals("")) {
			if ((task.getName() != null) && !task.getName().equals("")) {
				if ((task.getCreatorUid() != null) && !task.getCreatorUid().equals(""))
					try {
						// TODO Vérifier si a part le nom et le creatorUid,
						// d'autre informations peuvent être précisée
						final EntityTask entityTask = new EntityTask();
						entityTask.setUid(UUID.randomUUID().toString());
						entityTask.setName(task.getName());
						entityTask.setCreatorUid(task.getCreatorUid());
						if (!task.getDescription().isNil() && !StringUtils.isBlank(task.getDescription().getValue()))
							entityTask.setDescription(task.getDescription().getValue());
						if ((task.getCategory() != null) && !task.getCategory().isNil()
								&& !StringUtils.isBlank(task.getCategory().getValue()))
							entityTask.setDescription(task.getDescription().getValue());
						if ((task.getExpectedStartDate() != null) && !task.getExpectedStartDate().isNil())
							entityTask.setExpectedStartDate(task.getExpectedStartDate().getValue()
									.toGregorianCalendar().getTime());
						if ((task.getExpectedEndDate() != null) && !task.getExpectedEndDate().isNil())
							entityTask.setExpectedEndDate(task.getExpectedEndDate().getValue().toGregorianCalendar()
									.getTime());
						if (task.getPriority() == null)
							entityTask.setPriority(PRIORITY.NORMAL);
						else
							entityTask.setPriority(PRIORITY.valueOf(task.getPriority().toString()));

						entityTask.setStatus(STATUS.PENDING);

						getDao().create(entityTask);
						return transformEntityTask(entityTask);
					} catch (final Exception exception) {
						LOG.error("", exception);
						throw new TechnicalException("L'insertion de la tâche à échouée", 0);
					}
					else
						throw new BusinessException("L'uid de l'auteur de la tâche doit être précisé", 0);
			} else
				throw new BusinessException("Le nom de la tâche doit être précisée", 0);
		} else
			throw new BusinessException("Un uid ne peut être précisé lors de la création", 0);
	}

	@Override
	public void deleteTask(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		try {
			final EntityTask entityTask = new EntityTask();
			entityTask.setUid(task.getUid());
			getDao().remove(entityTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException("La suppression de la tâche à échouée", 0);
		}

	}

	@Override
	public Task increasePriority(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setPriority(PRIORITY.HIGH);
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task lowPriority(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setPriority(PRIORITY.LOW);
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public TaskList searchTask(final Task taskCriteria) throws TechnicalException {
		final boolean descriptionIsNull = ((taskCriteria.getDescription() != null) && taskCriteria.getDescription()
				.isNil());
		final boolean categoryIsNull = ((taskCriteria.getCategory() != null) && taskCriteria.getCategory().isNil());
		final boolean expectedEndDateIsNull = ((taskCriteria.getExpectedEndDate() != null) && taskCriteria
				.getExpectedEndDate().isNil());
		final boolean expectedStartDateIsNull = ((taskCriteria.getExpectedStartDate() != null) && taskCriteria
				.getExpectedStartDate().isNil());
		final boolean assignedUsersIsNull = ((taskCriteria.getAssignedUsersUids() != null) && taskCriteria
				.getAssignedUsersUids().isNil());
		final List<EntityTask> tasks = getDao().findTasks(transformWSTaskToCriteria(taskCriteria), descriptionIsNull,
				categoryIsNull, expectedEndDateIsNull, expectedStartDateIsNull, assignedUsersIsNull);
		final TaskList wsTasks = OBJECT_FACTORY.createTaskList();
		for (final EntityTask task : tasks)
			try {
				wsTasks.getTaskDocument().add(transformEntityTask(task));
			} catch (final DatatypeConfigurationException exception) {
				LOG.error("", exception);
				throw new TechnicalException("An error occur during date transformation", -1);
			}
			return wsTasks;
	}

	@Override
	public TaskList searchTasksAssignedToUsers(final UserUidList usersUid) throws TechnicalException {
		final TaskList tasks = OBJECT_FACTORY.createTaskList();
		final List<EntityTask> entityTasks = getDao().findTasksByUsers(usersUid.getUserUid());
		try {
			for (final EntityTask entityTask : entityTasks)
				tasks.getTaskDocument().add(transformEntityTask(entityTask));
		} catch (final DatatypeConfigurationException exception) {
			LOG.error("", exception);
			throw new TechnicalException("An error occur during date transformation", -1);
		}
		return tasks;
	}

	@Override
	public Task startTask(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setStatus(STATUS.PROGRESS);
		existingTask.setStartDate(new Date());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task suspendTask(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setStatus(STATUS.PENDING);
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task unspecifyPriority(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setPriority(PRIORITY.NORMAL);
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}

	}

	@Override
	public Task updateExpectedEndDate(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		if (task.getExpectedEndDate().isNil())
			existingTask.setExpectedEndDate(null);
		else if (task.getExpectedEndDate().getValue() != null)
			existingTask.setExpectedEndDate(task.getExpectedEndDate().getValue().toGregorianCalendar().getTime());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task updateExpectedStartDate(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setExpectedStartDate(task.getExpectedStartDate().getValue().toGregorianCalendar().getTime());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task updateTaskDescription(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		if (task.getDescription() != null)
			if (task.getDescription().isNil())
				existingTask.setDescription(null);
			else
				existingTask.setDescription(task.getDescription().getValue());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task updateTaskName(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		existingTask.setName(task.getName());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task updateTaskCategory(final Task task) throws BusinessException, TechnicalException {
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		if (task.getCategory() != null)
			if (task.getCategory().isNil())
				existingTask.setCategory(null);
			else
				existingTask.setCategory(task.getCategory().getValue());
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task assignUsersToTask(final Todo todo) throws BusinessException, TechnicalException {
		final Task task = todo.getTask();
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		for (final String userUid : todo.getUserToAffect().getUserUid()) {
			final EntityTodo entityTodo = new EntityTodo(new TodoKey(task.getUid(), userUid));
			if (!existingTask.getTodo().contains(entityTodo)) {
				getDao().getEntityManager4TASK().persist(entityTodo);//TODO IMPROVE
				existingTask.getTodo().add(entityTodo);
			}
		}
		try {
			getDao().update(existingTask);
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	@Override
	public Task unassignedUsersToTask(final Todo todo) throws BusinessException, TechnicalException {
		final Task task = todo.getTask();
		checkUid(task);
		final EntityTask existingTask = getTaskByUid(task.getUid());
		for (final String userUid : todo.getUserToAffect().getUserUid()){
			final TodoKey key = new TodoKey(task.getUid(), userUid);
			final EntityTodo entityTodo = new EntityTodo(key);
			if (existingTask.getTodo().contains(entityTodo)){
				existingTask.getTodo().remove(entityTodo);
				getDao().update(existingTask);
				getDao().getEntityManager4TASK().remove(getDao().getEntityManager4TASK().find(EntityTodo.class, key));//TODO IMPROVE
			}
		}
		try {
			return transformEntityTask(existingTask);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_UPDATE_FAILED, 0);
		}
	}

	private void checkUid(final Task task) throws BusinessException {
		if ((task.getUid() == null) || task.getUid().equals("")) {
			LOG.warn("uid absent");
			throw new BusinessException(Error.TASK_UID_MISSING, 0);
		}
	}

	private Task transformEntityTask(final EntityTask entityTask) throws DatatypeConfigurationException {
		assert entityTask.getUid() != null;
		final Task task = OBJECT_FACTORY.createTask();
		task.setUid(entityTask.getUid());
		assert entityTask.getName() != null;
		task.setName(entityTask.getName());
		if (entityTask.getDescription() != null)
			task.setDescription(OBJECT_FACTORY.createTaskDescription(entityTask.getDescription()));
		if (entityTask.getCategory() != null)
			task.setCategory(OBJECT_FACTORY.createTaskCategory(entityTask.getCategory().toString()));
		task.setCreatorUid(entityTask.getCreatorUid());
		if (entityTask.getExpectedStartDate() != null)
			task.setExpectedStartDate(OBJECT_FACTORY.createTaskExpectedStartDate(transformToXMLDate(entityTask
					.getExpectedStartDate())));
		if (entityTask.getExpectedEndDate() != null)
			task.setExpectedEndDate(OBJECT_FACTORY.createTaskExpectedEndDate(transformToXMLDate(entityTask
					.getExpectedEndDate())));
		if (entityTask.getStartDate() != null)
			task.setStartDate(OBJECT_FACTORY.createTaskStartDate(transformToXMLDate(entityTask.getStartDate())));
		if (entityTask.getEndDate() != null)
			task.setEndDate(OBJECT_FACTORY.createTaskEndDate(transformToXMLDate(entityTask.getEndDate())));
		assert entityTask.getPriority() != null;
		task.setPriority(PriorityType.valueOf(entityTask.getPriority().toString()));
		assert entityTask.getStatus() != null;
		task.setStatus(StatusType.valueOf(entityTask.getStatus().toString()));
		task.setAssignedUsersUids(OBJECT_FACTORY.createTaskAssignedUsersUids(OBJECT_FACTORY.createUserUidList()));
		if (entityTask.getTodo().size() > 0)
			for (final EntityTodo todo : entityTask.getTodo())
				task.getAssignedUsersUids().getValue().getUserUid().add(todo.getUserUid());
		else
			task.getAssignedUsersUids().setNil(true);
		return task;
	}

	@Override
	public Comment createComment(final Comment comment) throws BusinessException, TechnicalException {
		if (StringUtils.isBlank(comment.getWriterUid()))
			throw new BusinessException(Error.COMMENT_WRITER_UID_MISSING, 0);
		if ((comment.getTask() == null) || StringUtils.isBlank(comment.getTask().getUid()))
			throw new BusinessException(Error.COMMENT_TASK_UID_MISSING, 0);
		if (StringUtils.isBlank(comment.getText()))
			throw new BusinessException(Error.COMMENT_TEXT_MISSING, 0);
		final EntityComment commentToAdd = new EntityComment();
		commentToAdd.setUid(UUID.randomUUID().toString());
		commentToAdd.setText(comment.getText());
		commentToAdd.setWriterUid(comment.getWriterUid());
		commentToAdd.setCreationDate(new Date());
		final EntityTask entityTask = getTaskByUid(comment.getTask().getUid());
		commentToAdd.setTask(entityTask);
		getDao().create(commentToAdd);
		entityTask.getComments().add(commentToAdd);
		getDao().update(entityTask);
		try {
			return transformEntityComment(commentToAdd);
		} catch (final DatatypeConfigurationException e) {
			LOG.error("", e);
			throw new TechnicalException("Impossible de créer le commentaire", 0);
		}
	}

	@Override
	public void deleteComment(final Comment comment) throws BusinessException, TechnicalException {
		if (StringUtils.isBlank(comment.getUid())) {
			LOG.warn("uid absent");
			throw new BusinessException(Error.COMMENT_UID_MISSING, 0);
		}
		final EntityComment commentToRemove = new EntityComment();
		commentToRemove.setUid(comment.getUid());
		getDao().remove(commentToRemove);
	}

	@Override
	public CommentList getCommentsByTask(final Task task) throws BusinessException, TechnicalException {
		final CommentList commentList = OBJECT_FACTORY.createCommentList();
		final List<EntityComment> comments = getDao().getComments(transformWSTaskToCriteria(task));
		try {
			for (final EntityComment entityComment : comments)
				commentList.getCommentDocument().add(transformEntityComment(entityComment));
		} catch (final DatatypeConfigurationException exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.DATE_PROBLEM, 0);
		}
		return commentList;
	}

	private EntityTask transformWSTaskToCriteria(final Task task) {
		final EntityTask entityTask = new EntityTask();
		entityTask.setUid(task.getUid());
		entityTask.setName(task.getName());
		if ((task.getDescription() != null) && !task.getDescription().isNil())
			entityTask.setDescription(task.getDescription().getValue());
		if ((task.getCategory() != null) && !task.getCategory().isNil())
			entityTask.setCategory(task.getCategory().getValue());
		entityTask.setCreatorUid(task.getCreatorUid());
		if ((task.getExpectedEndDate() != null) && !task.getExpectedEndDate().isNil())
			entityTask.setExpectedEndDate(task.getExpectedEndDate().getValue().toGregorianCalendar().getTime());
		if ((task.getExpectedStartDate() != null) && !task.getExpectedStartDate().isNil())
			entityTask.setExpectedStartDate(task.getExpectedStartDate().getValue().toGregorianCalendar().getTime());
		if (task.getPriority() != null)
			entityTask.setPriority(PRIORITY.valueOf(task.getPriority().toString()));
		if (task.getStatus() != null)
			entityTask.setStatus(STATUS.valueOf(task.getStatus().toString()));
		if ((task.getStartDate() != null) && !task.getStartDate().isNil())
			entityTask.setStartDate(task.getStartDate().getValue().toGregorianCalendar().getTime());
		if ((task.getEndDate() != null) && !task.getEndDate().isNil())
			entityTask.setEndDate(task.getEndDate().getValue().toGregorianCalendar().getTime());
		entityTask.setCreatorUid(task.getCreatorUid());
		if ((task.getAssignedUsersUids() != null) && !task.getAssignedUsersUids().isNil())
			for (final String userUid : task.getAssignedUsersUids().getValue().getUserUid()) {
				final EntityTodo todo = new EntityTodo(new TodoKey(task.getUid(), userUid));
				entityTask.getTodo().add(todo);
			}

		return entityTask;
	}

	private XMLGregorianCalendar transformToXMLDate(final Date date) throws DatatypeConfigurationException {
		final GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(date);
		return DatatypeFactory.newInstance().newXMLGregorianCalendar(calendar);
	}

	private EntityTask getTaskByUid(final String uid) throws TechnicalException {
		EntityTask existingTask = null;
		try {
			existingTask = getDao().findTaskByUid(uid);
		} catch (final Exception exception) {
			LOG.error("", exception);
			throw new TechnicalException(Error.TASK_SEARCH_FAILED, 0);
		}
		if (existingTask == null)
			throw new TechnicalException(Error.TASK_SEARCH_FAILED, 0);
		return existingTask;
	}

	private Comment transformEntityComment(final EntityComment comment) throws DatatypeConfigurationException {
		final Comment wsComment = OBJECT_FACTORY.createComment();
		wsComment.setUid(comment.getUid());
		wsComment.setText(comment.getText());
		wsComment.setWriterUid(comment.getWriterUid());
		wsComment.setCreationDate(transformToXMLDate(comment.getCreationDate()));
		final Task task = OBJECT_FACTORY.createTask();
		task.setUid(comment.getTask().getUid());
		wsComment.setTask(task);
		return wsComment;
	}

}
